//==============================================================================
//
//          DDateTime - the date & time datatype in the ofc-library
//
//               Copyright (C) 2004  Dick van Oudheusden
//  
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 3 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free
// Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
//
//==============================================================================
// 
//  $Date: 2008-07-20 14:03:51 $ $Revision: 1.9 $
//
//==============================================================================

#include "ofc/DDateTime.h"

#ifdef WIN32
#include <windows.h>
#else
#include <sys/time.h>
#endif
#include <time.h>
#include <ctype.h>
#include <string.h>

#if _INTERFACE_

#include "ofc/config.h"

#include "ofc/DText.h"
#include "ofc/DTextable.h"
#include "ofc/DParsable.h"
#include "ofc/DComparable.h"

#define DDT_SUNDAY      (0)
#define DDT_MONDAY      (1)
#define DDT_TUESDAY     (2) 
#define DDT_WEDNESDAY   (3)
#define DDT_THURSDAY    (4)
#define DDT_FRIDAY      (5)
#define DDT_SATURDAY    (6)

#define DDT_MIN_YEAR    (1970)

//
// The DDateTime class implements a date & time datatype.
//
// @example
// #include <stdio.h>
// #include "ofc/DDateTime.h"
// 
// int main(int argc, char *argv[])
// {
//   DDateTime *dtm1 = [DDateTime alloc];
//   DDateTime *dtm2 = [DDateTime new  ];
//   DText     *str;
// 
//   [dtm1 init :2008 :7 :20 :15 :3 :45]; // Init with a date and time
// 
//   [dtm2 time :16 :50 :8];              // Set the time
//   [dtm2 date :2007 :12 :31];           // Set the date
// 
//   str = [dtm1 toISO8601];
//   printf("Date1 in ISO8601 format:%s.\n", [str cstring]);
//   [str free];
// 
//   [dtm2 localTime];                    // Set the date&time with localtime
// 
//   str = [dtm2 toASC];
//   printf("LocalTime in asctime format:%s.\n", [str cstring]);
//   [str free];
// 
//   [dtm2 UTCTime];                      // Set the date&time with UTC time
//   str = [dtm2 format :"%a, %d-%b-%Y %H:%M:%S %z"];
//   printf("UTCTime with format:%s.\n", [str cstring]);
//   [str free];
// 
//   if ([dtm1 compare :dtm2] == 0)       // Compare two dates
//     printf("Date1 is equal to date2.\n");
//   else if ([dtm1 compare :dtm2] < 0)
//     printf("Date1 is smaller than date2.\n");
//   else
//     printf("Date1 is greater than date2.\n");
// 
//   [dtm1 free];                         // Cleanup
//   [dtm2 free];
// 
//   return 0;
// }
// 

@interface DDateTime : DObject <DTextable,DParsable,DComparable>
{
@private
  int                 _millis;    // the milliseconds (0..999)
  int                 _seconds;   // the seconds (0..59)
  int                 _minutes;   // the minutes (0..59)
  int                 _hours;     // the hours (0..23)
  int                 _day;       // the day (1..31)
  int                 _month;     // the month (1..12)
  int                 _year;      // the year (1970..)
  int                 _weekday;   // the week day 0..6
}

#endif


#define USECS_PER_MSEC (1000L)

static int  _daysInMonth[]   = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};

static char _ISOFormat[]     = "%04d-%02d-%02dT%02d:%02d:%02d";
static char _ASCFormat[]     = "%a %b %d %H:%M:%S %Y";
static char _RFC850Format[]  = "%A, %d-%b-%y %H:%M:%S";
static char _RFC1123Format[] = "%a, %d %b %Y %H:%M:%S";
static char _RFC822Format[]  = "%a, %d-%b-%Y %H:%M:%S %z";


@implementation DDateTime

  
//// Constructors

//
// Initialise the object with a default date
//
// @return the object
//
- (DDateTime *) init
{
  [super init];

  _millis         = 0;
  _seconds        = 0;
  _minutes        = 0;
  _hours          = 0;
  _day            = 1;
  _month          = 1;
  _year           = DDT_MIN_YEAR;
  
  _weekday        = 0;
  
  return self;
}

//
// Initialise with a date/time
//
// @param year     the year (1970..)
// @param month    the month (1..12)
// @param day      the day (1..31)
// @param hours    the hours (0..23)
// @param minutes  the minutes (0..59)
// @param seconds  the seconds (0..59)
// 
// @return the object
//
- (DDateTime *) init :(int) year :(int) month :(int) day :(int) hours :(int) minutes :(int) seconds
{
  [self init];

  [self set :year :month :day :hours :minutes :seconds];
  
  return self;
}


//// Member methods

//
// Return the year
// 
// @return the year (1970..)
//
- (int) year
{
  return _year;
}

//
// Return the month
// 
// @return the month (1..12)
// 
- (int) month
{
  return _month;
}

//
// Return the day
// 
// @return the day (1..31)
// 
- (int) day
{
  return _day;
}

//
// Return the hours
// 
// @return the hours (0..23)
// 
- (int) hours
{
  return _hours;
}

//
// Return the minutes
// 
// @return the minutes (0..59)
// 
- (int) minutes
{
  return _minutes;
}

//
// Return the seconds
// 
// @return the seconds (0..59)
// 
- (int) seconds
{
  return _seconds;
}

//
// Return the milliseconds
// 
// @return the milliseconds (0..999)
// 
- (int) millis
{
  return _millis;
}

//
// Return the day of the week 
// 
// @return the day of the week (0..6)
// 
- (int) weekday
{
  return _weekday;
}


//// Class methods

//
// Check if a year is a leap year
//
// @param year     the year to be checked
//
// @return is it ?
//

+ (BOOL) isLeapYear :(int) year
{
  return (year % 4 == 0) && ((year % 100 != 0) || (year % 400 == 0));
}

//
// Return the number of days in a month
//
// @param year     the year of the month
// @param month    the month (1..12)
//
// @return the number of days in the month (1..31) or (-1)
//

+ (int) daysInMonth :(int) year :(int) month
{
  int days = -1;
  if ((month >= 1) && (month <= 12))
  {
    days = _daysInMonth[month-1] + (((month == 2) && ([DDateTime isLeapYear :year])) ? 1 : 0);
  }
  else
    WARNING(DW_ARG_OUT_RANGE, "month");
    
  return days;
}

//
// Check if the date is valid
// 
// @param year     the year (1970..)
// @param month    the month (1..12)
// @param day      the day (1..31)
// 
// @return is it ?
// 
+ (BOOL) isDateValid :(int) year :(int) month :(int) day
{
  BOOL ok = YES;
  
  if (year < DDT_MIN_YEAR)
  {
    ok = NO;
  }
  else if ((month < 1) || (month > 12))
  {
    ok = NO;
  }
  else if ((day < 1) || (day > [DDateTime daysInMonth :year :month]))
  {
    ok = NO;
  }
  
  return ok;
}

//
// Check if the time is valid
// 
// @param hours    the hours (0..23)
// @param minutes  the minutes (0..59)
// @param seconds  the seconds (0..59)
// @param millis   the milliseconds (0..999)
// 
// @return is it ?
// 
+ (BOOL) isTimeValid :(int) hours :(int) minutes :(int) seconds :(int) millis
{
  BOOL ok = YES;
  
  if ((hours < 0) || (hours > 23))
  {
    ok = NO;
  }
  else if ((minutes < 0) || (minutes > 59))
  {
    ok = NO;
  }
  else if ((seconds < 0) || (seconds > 59))
  {
    ok = NO;
  }
  else if ((millis < 0) || (millis > 999))
  {
    ok = NO;
  }
  
  return ok;
}

//
// Check if the date and time are valid
// 
// @param year     the year (1970..)
// @param month    the month (1..12)
// @param day      the day (1..31)
// @param hours    the hours (0..23)
// @param minutes  the minutes (0..59)
// @param seconds  the seconds (0..59)
// @param millis   the milliseconds (0..999)
// 
// @return is it ?
// 
+ (BOOL) isValid :(int) year :(int) month :(int) day :(int) hours :(int) minutes :(int) seconds :(int) millis
{
  return (([DDateTime isDateValid :year :month :day]) && 
          ([DDateTime isTimeValid :hours :minutes :seconds :millis]));
}


//// Set methods

// 
// Set the date/time
//
// @param year     the year (1970..)
// @param month    the month (1..12)
// @param day      the day (1..31)
// @param hours    the hours (0..23)
// @param minutes  the minutes (0..59)
// @param seconds  the seconds (0..59)
// 
// @return success
// 
- (BOOL) set :(int) year :(int) month :(int) day :(int) hours :(int) minutes :(int) seconds
{
  return [self set :year :month :day :hours :minutes :seconds :0];
}

// 
// Set the date/time
//
// @param year     the year (1970..)
// @param month    the month (1..12)
// @param day      the day (1..31)
// @param hours    the hours (0..23)
// @param minutes  the minutes (0..59)
// @param seconds  the seconds (0..59)
// @param millis   the milliseconds (0..999)
// 
// @return success
// 
- (BOOL) set :(int) year :(int) month :(int) day :(int) hours :(int) minutes :(int) seconds :(int) millis
{
  BOOL ok = NO;
  
  if (![DDateTime isDateValid :year :month :day])
  {
    WARNING(DW_INVALID_ARG, "date");
  }
  else if (![DDateTime isTimeValid :hours :minutes :seconds :millis])
  {
    WARNING(DW_INVALID_ARG, "time");
  }
  else
  {
    _year    = year;
    _month   = month;
    _day     = day;
    _hours   = hours;
    _minutes = minutes;
    _seconds = seconds;
    _millis  = millis;
    
    ok = [self norm];
  }
    
  return ok;
}

// 
// Set the time
//
// @param hours    the hours (0..23)
// @param minutes  the minutes (0..59)
// @param seconds  the seconds (0..59)
// 
// @return success
// 
- (BOOL) time :(int) hours :(int) minutes :(int) seconds
{
  return [self time :hours :minutes :seconds :0];
}

// 
// Set the time (incl. milliseconds)
//
// @param hours    the hours (0..23)
// @param minutes  the minutes (0..59)
// @param seconds  the seconds (0..59)
// @param millis   the milliseconds (0..999)
// 
// @return success
// 
- (BOOL) time :(int) hours :(int) minutes :(int) seconds :(int) millis
{
  BOOL ok = [DDateTime isTimeValid :hours :minutes :seconds :millis];
  
  if (!ok)
  {
    WARNING(DW_INVALID_ARG, "time");
  }
  else
  {
    _hours   = hours;
    _minutes = minutes;
    _seconds = seconds;
    _millis  = millis;
    
    ok = [self norm];
  }
    
  return ok;
}

// 
// Set the date
//
// @param year     the year (1970..)
// @param month    the month (1..12)
// @param day      the day (1..31)
// 
// @return success
// 
- (BOOL) date :(int) year :(int) month :(int) day
{
  BOOL ok = [DDateTime isDateValid :year :month :day];
  
  if (!ok)
  {
    WARNING(DW_INVALID_ARG, "date");
  }
  else
  {
    _year    = year;
    _month   = month;
    _day     = day;
    
    ok = [self norm];
  }
    
  return ok;
}

// 
// Set the object to the current local date/time
//
// @return success
// 
- (BOOL) localTime
{
  BOOL ok = YES;

#ifdef WIN32
  SYSTEMTIME date;

  GetLocalTime(&date);

  _millis         = date.wMilliseconds;
  _seconds        = date.wSecond;
  _minutes        = date.wMinute;
  _hours          = date.wHour;
  _day            = date.wDay;
  _month          = date.wMonth;
  _year           = date.wYear;
  
  _weekday        = date.wDayOfWeek;
  
#else
  struct timeval tv;
  
  ok = (gettimeofday(&tv, NULL) == 0);
  
  if (ok)
  {
    struct tm *date = NULL;
    
    _millis = tv.tv_usec / USECS_PER_MSEC; 
    
    date = localtime((time_t *) &tv.tv_sec);
    
    ok = (date != NULL);
    
    if (ok)
    {
      _seconds        = date->tm_sec;
      _minutes        = date->tm_min;
      _hours          = date->tm_hour;
      _day            = date->tm_mday;
      _month          = date->tm_mon + 1;
      _year           = date->tm_year + 1900;
  
      _weekday        = date->tm_wday;
    }
  }
#endif

  return ok;
}

// 
// Set the object to the current UTC date/time
//
// @return success
// 
- (BOOL) UTCTime
{
  BOOL ok = YES;

#ifdef WIN32
  SYSTEMTIME date;

  GetSystemTime(&date);
  
  _millis         = date.wMilliseconds;
  _seconds        = date.wSecond;
  _minutes        = date.wMinute;
  _hours          = date.wHour;
  _day            = date.wDay;
  _month          = date.wMonth;
  _year           = date.wYear;
  
  _weekday        = date.wDayOfWeek;
  
#else
  struct timeval tv;
  
  ok = (gettimeofday(&tv, NULL) == 0);
  
  if (ok)
  {
    struct tm *date = NULL;
    
    _millis = tv.tv_usec / USECS_PER_MSEC; 
    
    date = gmtime((time_t *) &tv.tv_sec);
    
    ok = (date != NULL);
    
    if (ok)
    {
      _seconds        = date->tm_sec;
      _minutes        = date->tm_min;
      _hours          = date->tm_hour;
      _day            = date->tm_mday;
      _month          = date->tm_mon + 1;
      _year           = date->tm_year + 1900;
  
      _weekday        = date->tm_wday;
    }
  }
#endif

  return ok;
}

// 
// Normalize the object
// 
// @return success
// 

- (BOOL) norm
{
  BOOL ok = YES;
  
  struct tm  date;

  memset(&date, 0, sizeof(struct tm));

  date.tm_year  = _year - 1900;
  date.tm_mon   = _month - 1;
  date.tm_mday  = _day;
  date.tm_hour  = _hours;
  date.tm_min   = _minutes;
  date.tm_sec   = _seconds;
  
  mktime(&date);
  
  _seconds        = date.tm_sec;
  _minutes        = date.tm_min;
  _hours          = date.tm_hour;
  _day            = date.tm_mday;
  _month          = date.tm_mon + 1;
  _year           = date.tm_year + 1900;
  
  _weekday        = date.tm_wday;

  return ok;
}


//// String output methods

//
// Format the date/time as specified in ISO 8601 (without the milliseconds and 'T' as separator)
// 
// @return a (new) text string object
// 
- (DText *) toISO8601
{
  DText *text = [DText new];
  
  [text format :_ISOFormat, _year, _month, _day, _hours, _minutes, _seconds];
  
  return text;
}

//
// Format the date/time as specified in RFC1123
// 
// @return a (new) text string object (or nil)
//
- (DText *) toRFC1123
{
  return [self format :_RFC1123Format];
}

//
// Format the date/time as specified in RFC850
// 
// @return a (new) text string object (or nil)
// 
- (DText *) toRFC850
{
  return [self format :_RFC850Format];
}

//
// Format the date/time as specified in RFC822
// 
// @return a (new) text string object (or nil)
// 
- (DText *) toRFC822
{
  return [self format :_RFC822Format];
}

//
// Format the date/time as  returned by asctime
// 
// @return a (new) text string object (or nil)
// 
- (DText *) toASC
{
  return [self format :_ASCFormat];
}

//
// Format the date/time accordingly a format string (see strftime)
// 
// @param format    the format c-string
// 
// @return a (new) text string object (or nil)
// 
- (DText *) format :(const char *) format
{
  DText     *text = [DText new];
  struct tm  date;
  char       buffer[256];
  
  memset(&date, 0, sizeof(struct tm));

  date.tm_year  = _year - 1900;
  date.tm_mon   = _month - 1;
  date.tm_mday  = _day;
  date.tm_hour  = _hours;
  date.tm_min   = _minutes;
  date.tm_sec   = _seconds;
  
  mktime(&date);

  if (strftime(buffer, sizeof(buffer), format, &date) != 0)
  {
    text = [DText new];
    
    [text set :buffer];
  }
  
  return text;
}


//// DTextable protocol implementation

//
// Format the date/time object as text (ISO 8601 format with milliseconds and ' ' as separator)
// 
// @return a (new) text string object
//
- (DText *) toText
{
  DText *text = [DText new];
  
  [text format :"%04d-%02d-%02d %02d:%02d:%02d.%03d", _year, _month, _day, _hours, _minutes, _seconds, _millis];
  
  return text;
}


//// DParsable protocol implementation

//
// Parse a string to set a date time object.
// This method supports the following format: <br>
//  yyyy-mm-dd hh:mm:ss.mmm -- date/time accordingly ISO 8061 <br>
//  milliseconds are optional, also the separators
//
// @param cstr     the string to be parsed (moved to first non-parsable char)
// 
// @return the result (0, ERANGE, ENODATA)
//

- (int) fromString :(char **) cstr
{
  int   result = ENODATA;

  result = [self date :cstr];
  
  if (result != ERANGE)
  {
    result = [self time :cstr];
  }
  
  return result;
}

#if _PRIVATE_

//
// Scan the string for a fix length integer
// 
// @param number    the number of characters to be scanned
// @param cstr      the string to be scanned
// @param value     the scanned value
// @param min       the minimum allowed value
// @param max       the maximum allowed value
// 
// @return is a valid value scanned ?
// 
static BOOL _scan(int number, char **cstr, int *value, int min, int max)
{
  BOOL  ok   = NO;
  
  char *pntr = *cstr;
  
  *value = 0;
  
  while ((number > 0) && (isdigit(*pntr)))
  {
    *value = *value * 10 + (int) (*pntr++ - '0');
    
    number--;
  }
  
  if (number == 0)
  {
    if ((*value >= min) && (*value <= max))
    {
      *cstr = pntr;
      
      ok = YES;
    }
  }
  
  return (ok);
}
#endif

//
// Parse a string to set the object with a date.
// This method supports the following format: (ISO 8061)<br>
//  yyyy-mm-dd yy-mm-dd yyyymmdd yymmdd
//
// @param cstr     the string to be parsed (moved to first non-parsable char)
// 
// @return the result (0, ERANGE, ENODATA)
//
- (int) date :(char **) cstr
{
  int   result = 0;
  char *pntr   = *cstr;
  char *origin = pntr;
  int   digits = 0;
  int   seps   = 0;
  int   year   = 0;
  int   month  = 0;
  int   day    = 0;
  
  while (isspace(*pntr))
  {
    pntr++;
  }

  origin = pntr;
  
  while ((isdigit(*pntr)) || (*pntr == '-'))
  {
    if (*pntr == '-')
    {
      seps++;
    }
    else
    {
      digits++;
    }
    pntr++;
  }

  
  if (((seps == 0) || (seps == 2)) && ((digits == 6) || (digits == 8)))
  {
    pntr = origin;
    
    if (digits == 6)
    {
      // No century
      if (_scan(2, &pntr, &year, 0, 99))
      {
        year = (year < 70) ? year + 2000 : year + 1900;
      }
      else
      {
        result = ERANGE;
      }
    }
    else // (digits == 8)
    {
      // Full year
      if (!_scan(4, &pntr, &year, 1970, 9999))
      {
        result = ERANGE;
      }
    }
    
    if (result == 0)
    {
      if (*pntr == '-')
      {
        pntr++;
      }

      if (!_scan(2, &pntr, &month, 1, 12))
      {
        result = ERANGE;
      }
    }
    
    if (result == 0)
    {
      if (*pntr == '-')
      {
        pntr++;
      }
      
      if (!_scan(2, &pntr, &day, 1, 31))
      {
        result = ERANGE;
      }
    }
  }
  else
  {
    result = ENODATA;
  }
  
  if (result == 0)
  {
    _year  = year;
    _month = month;
    _day   = day;
    
    [self norm];
    
    *cstr = pntr;
  }
  
  return result;
}

//
// Parse a string to set a date time object with a time.
// This method supports the following format: (ISO 8061)<br>
//  hh:mm:ss.mmm hh:mm:ss hhmmss.mmm hhmmss hh:mm hhmm
//
// @param cstr     the string to be parsed (moved to first non-parsable char)
// 
// @return the result (0, ERANGE, ENODATA)
//
- (int) time :(char **) cstr
{
  int   result  = 0;
  char *pntr    = *cstr;
  char *origin  = pntr;
  int   digits  = 0;
  int   seps    = 0;
  int   millis  = 0;
  int   hours   = 0;
  int   minutes = 0;
  int   seconds = 0;
  
  while (isspace(*pntr))
  {
    pntr++;
  }

  if (*pntr == 'T')
  {
    pntr++;
  }
  
  origin = pntr;
  
  while ((isdigit(*pntr)) || (*pntr == ':') || (*pntr == '.'))
  {
    if (*pntr == ':')
    {
      seps++;
    }
    else if (*pntr == '.')
    {
      millis++;
    }
    else
    {
      digits++;
    }
    
    pntr++;
  }

  
  if (((digits >= 7) && (digits <= 9) && (millis == 1)) ||
      ((digits == 6) && (seps <= 2) && (millis == 0)) ||
      ((digits == 4) && (seps <= 1) && (millis == 0)))
  {
    pntr = origin;

    millis = 0;
    
    if (!_scan(2, &pntr, &hours, 0, 23))
    {
      result = ERANGE;
    }
    
    if (result == 0)
    {
      if (*pntr == ':')
      {
        pntr++;
      }
      
      if (!_scan(2, &pntr, &minutes, 0, 59))
      {
        result = ERANGE;
      }
    }
    
    if ((result == 0) && (digits > 4))
    {
      if (*pntr == ':')
      {
        pntr++;
      }
      
      if (!_scan(2, &pntr, &seconds, 0, 59))
      {
        result = ERANGE;
      }
    }
    
    if ((result == 0) && (digits > 7))
    {
      if (*pntr == '.')
      {
        pntr++;
        
        if (!_scan((digits - 6), &pntr, &millis, 0, 999))
        {
          result = ERANGE;
        }
      }
      else
      {
        result = ERANGE;
      }
    }
  }
  else
  {
    result = ENODATA;
  }
  
  if (result == 0)
  {
    _hours   = hours;
    _minutes = minutes;
    _seconds = seconds;
    _millis  = millis;
    
    [self norm];
    
    *cstr = pntr;
  }
  
  return result;
}


//// String parse methods (requires strptime)

#ifdef HAVE_STRPTIME
#define HAVE_DDATETIME_PARSE (1)

extern char *strptime(const char *, const char *, struct tm *); // not always defined in time.h
#endif

//
// Parse a string with a format string to set the date/time object (requires strptime)
// 
// @param cstr     the string to be parsed (moved to the first non-parsable char)
// @param format   the format for the string (see strptime)
// 
// @return is the string parsed ?
// 
- (BOOL) parse :(char **) cstr :(const char *) format
{
  char      *pntr   = NULL;

#ifdef HAVE_STRPTIME
  struct tm  date;
  
  memset(&date, 0, sizeof(struct tm));

  date.tm_year  = _year - 1900;
  date.tm_mon   = _month - 1;
  date.tm_mday  = _day;
  date.tm_hour  = _hours;
  date.tm_min   = _minutes;
  date.tm_sec   = _seconds;
  
  pntr = strptime(*cstr, format, &date);
  
  if (pntr != NULL)
  {
    _seconds        = date.tm_sec;
    _minutes        = date.tm_min;
    _hours          = date.tm_hour;
    _day            = date.tm_mday;
    _month          = date.tm_mon + 1;
    _year           = date.tm_year + 1900;
    _weekday        = date.tm_wday;
    
    *cstr = pntr;
  }
#else
  WARNING(DW_METHOD_NOT_IMPL, "parse::");
#endif
  
  return (pntr != NULL);
}

//
// Parse the string as specified in RFC1123 to set the date/time object (requires strptime)
//
// @param cstr     the string to be parsed (moved to first non-parsable char)
// 
// @return is the string parsed ?
//
- (BOOL) fromRFC1123 :(char **) cstr
{
  return [self parse :cstr :_RFC1123Format];
}

//
// Parse the string as specified in RFC850 to set the date/time object (required strptime)
// 
// @param cstr     the string to be parsed (moved to first non-parsable char)
// 
// @return is the string parsed ?
// 
- (BOOL) fromRFC850 :(char **) cstr
{
  return [self parse :cstr :_RFC850Format];
}

// Parse the string as specified in RFC822 to set the date/time object (requires strptime)
//
// @param cstr     the string to be parsed (moved to first non-parsable char)
// 
// @return is the string parsed ?
//
- (BOOL) fromRFC822 :(char **) cstr
{
  return [self parse :cstr :_RFC822Format];
}

//
// Parse the string with the format of asctime to set the date/time object (requires strptime)
// 
// @param cstr     the string to be parsed (moved to first non-parsable char)
// 
// @return is the string parsed ?
// 
- (BOOL) fromASC :(char **) cstr
{
  return [self parse :cstr :_ASCFormat];
}


//// Comparable protocol implementation

#if _PRIVATE_

//
// Compare two integers
// 
// @param c1        the first integer
// @param c2        the second integer
// 
// @return the compare result (-1,0,1)
// 
static int _compare(int c1, int c2)
{
  if (c1 < c2)
  {
    return -1;
  }
  else if (c1 > c2)
  {
    return 1;
  }
  else
  {
    return 0;
  }
}

#endif

//
// Compare the DateTime object with another DateTime object
//
// @param other    the object that should be compared
//
// @return the compare result (-1, 0, 1)
//

- (int) compare :(DDateTime *) other
{
  int result = 0;
  
  result = _compare(_year, other->_year);
  
  if (result == 0)
  {
    result = _compare(_month, other->_month);
  }
  
  if (result == 0)
  {
    result = _compare(_day, other->_day);
  }
  
  if (result == 0)
  {
    result = _compare(_hours, other->_hours);
  }
  
  if (result == 0)
  {
    result = _compare(_minutes, other->_minutes);
  }
  
  if (result == 0)
  {
    result = _compare(_seconds, other->_seconds);
  }
  
  if (result == 0)
  {
    result = _compare(_millis, other->_millis);
  }
  
  return result;
}

@end

/*===========================================================================*/
